Introducción
José es un diseñador de juegos de mesa. Crea las reglas, diseña los gráficos, escoge su tema, número de jugadores y duración promedio del juego que tiene en mente. José es una persona tímida, y a pesar de que sus juegos suelen gustarle a sus amigos, él nunca ha querido publicarlos por miedo a que no sean bien recibidos. Se quiere demostrar a José, con una base de datos de calificaciones históricas de juegos de mesa, cómo hubieran sido recibidos sus juegos en promedio en la época que los fue creando.
Los datos a utilizar vienen de esta base de datos: (board_games)* que, en cambio, vienen de la página Board Game Geek.
Cargar Librerías
Usando ‘library’ cargamos las librerías, con las cuales vas a hacer uso de las diferentes funciones.
package 㤼㸱data.table㤼㸲 was built under R version 4.0.5data.table 1.14.0 using 4 threads (see ?getDTthreads). Latest news: r-datatable.com
Attaching package: 㤼㸱data.table㤼㸲
The following objects are masked from 㤼㸱package:h2o㤼㸲:
hour, month, week, year
library("h2o")
library("ggplot2")
library("ggthemes")
library("data.tree")
package 㤼㸱data.tree㤼㸲 was built under R version 4.0.5
package 㤼㸱tidyverse㤼㸲 was built under R version 4.0.5Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
-- Attaching packages ------------------------------------------------------- tidyverse 1.3.1 --
v tibble 3.1.0 v dplyr 1.0.5
v tidyr 1.1.3 v stringr 1.4.0
v readr 1.4.0 v forcats 0.5.1
v purrr 0.3.4
package 㤼㸱tidyr㤼㸲 was built under R version 4.0.5package 㤼㸱readr㤼㸲 was built under R version 4.0.5package 㤼㸱purrr㤼㸲 was built under R version 4.0.5package 㤼㸱dplyr㤼㸲 was built under R version 4.0.5package 㤼㸱stringr㤼㸲 was built under R version 4.0.5package 㤼㸱forcats㤼㸲 was built under R version 4.0.5-- Conflicts ---------------------------------------------------------- tidyverse_conflicts() --
x dplyr::between() masks data.table::between()
x dplyr::filter() masks stats::filter()
x dplyr::first() masks data.table::first()
x dplyr::lag() masks stats::lag()
x dplyr::last() masks data.table::last()
x purrr::transpose() masks data.table::transpose()
package 㤼㸱modeldata㤼㸲 was built under R version 4.0.5
library("DataExplorer")
library("vtree")
library("caTools")
package 㤼㸱caTools㤼㸲 was built under R version 4.0.5
package 㤼㸱rpart㤼㸲 was built under R version 4.0.5
package 㤼㸱rpart.plot㤼㸲 was built under R version 4.0.5
Ánalisis Descriptivo, Data Engineering
Leemos nuestro dataset
En este caso usamos read.csv, proceedemos a leer:
board_games <- read.csv("./board_games.csv")
Observación de las primeras líneas
Para ejemplificar usaremos el data frame turbines, y con la función head() solo mostraremos las pirmeras 6 líneas de este:
- game_id Identificador único
- description Descripción corta
- image URL con imagen del juego
- max_players Jugadores máximos
- max_playtime Tiempo máximo de juego
- min_age Edad mínima
- min_players Jugadores mínimos
- min_playtime Tiempo mínimo de juego
- name Nombre del juego
- playing_time Tiempo promedio de juego
- thumbnail URL con thumbnail del juego
- year_published Año de publicación
- artist Diseñador gráfico del juego
- category Categorías del juego (separadas por coma)
- compilation Si es parte de una compilación, nombre de la compilación
- designer Diseñador del juego
- expansion Si hay una expansión, el nombre de la expansión
- family Familia, equivalente a editora
- mechanic Mecánicas, separadas por coma
- publisher Compañía o persona que publicaron el juego (separadas por coma)
- average_rating Calificación promedio en Board Game Geek
- users_rated Número de usuarios que calificaron el juego
Colnames de nuestro dataset
Después de una rápida observación, ejecutamos los siguientes comandos para confirmación:
[1] "game_id" "description" "image" "max_players" "max_playtime"
[6] "min_age" "min_players" "min_playtime" "name" "playing_time"
[11] "thumbnail" "year_published" "artist" "category" "compilation"
[16] "designer" "expansion" "family" "mechanic" "publisher"
[21] "average_rating" "users_rated"
Tipo de variables
Usando data explorer observamos el tipo de variables, casi tenemos el mismo porcentaje para las discretas y continua, y tenemos un bajo porcentaje de missing values:
- Sólo el 0.99% de las filas están completas,
- tenemos 11.54% de observaciones faltantes, es decir, dado que solo tenemos 0.99% de las filas completas, solo hay 10.55% de observaciones faltantes del total.
Estos valores faltantes nos podrán general problemas para analizar los datos, veamos un poco los perfiles que faltan.

Missing plot
Para visualizar el perfil de los datos faltantes podemos utilizar la función plot_missing(). En la visualización debajo, podemos ver que la variables compilation y expansion, son las que les falta información, encontramos de que sólo el 2.63% (compilation), 16.54% (expansion) de nuestras filas estén completas y probablemente esta varible no sea de mucha infomación. Por tanto la podemos eliminar de nuestro dataframe, ahorita mismo!!
plot_missing(board_games)

Eliminamos la columna que tienes más missing values
Eliminamos notes de nuestro dataframe:
final_board_games <- drop_columns(board_games, c("compilation","expansion"))
colnames(final_board_games)
[1] "game_id" "description" "image" "max_players" "max_playtime"
[6] "min_age" "min_players" "min_playtime" "name" "playing_time"
[11] "thumbnail" "year_published" "artist" "category" "designer"
[16] "family" "mechanic" "publisher" "average_rating" "users_rated"
final_board_games <- na.omit(final_board_games)
Ánalisis de Correlación
Podemos ver la más alta correlación en estas variables:
- min_playtime-max_playtime
- min_playtime-min_age
- min_playtime-playing_time
- average_rating-min_age
plot_correlation(na.omit(final_board_games), maxcat = 5L)
Ignored all discrete features since `maxcat` set to 5 categories!

Ahora de una manera más detallada vamos a analizar las variables más correlacionadas entre si, este top de 10:
corr_cross(final_board_games, # name of dataset
max_pvalue = 0.05, # display only significant correlations (at 5% level)
top = 10 # display top 10 couples of variables (by correlation coefficient)
)
Returning only the top 10. You may override with the 'top' argument
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

QQ plot
La gráfica Quantile-Quantile es una forma de visualizar la desvisión de una distribución de probabilidad específica.
Después de analizar estos gráficos, a menudo es beneficioso aplicar una transformación matemática (como logaritmo) para modelos como la regresión lineal. Para hacerlo, podemos usar la función plot_qq. De forma predeterminada, se compara con la distribución normal.
qq_data <- final_board_games[, c("min_playtime", "max_playtime", "min_age", "playing_time", "average_rating")]
plot_qq(qq_data, sampled_rows = 1000L)

En el gráfico, las columnas parecen sesgadas en ambas colas. Apliquemos una transformación logarítmica simple y grafiquemos de nuevo. Average Rating no mejoró. Gabo si ves que es necesario podemos hacerle update a las columnas con la transformación.
log_qq_data <- update_columns(qq_data, 1:5, function(x) log(x + 1))
plot_qq(log_qq_data, sampled_rows = 1000L)

Ánalisis Exploratorio de los Datos
Teniendo nuestras variables con mayor correlación vamos a gráficarlas con geom point..:
- min_playtime-max_playtime


- average_rating-playing_time

- users_rated-average_rating

###Using vtree para explorar
Usamos vtree para observar la concentración de los datos por ejemplo para min_age, donde la mayoría de los datos se concentran en min_age de 8 años, 10 años y 12 años.
Usamos vtree para observar la concentración de los datos por ejemplo para min_players, tenemos casi un 69% para min 2 jugadores y cerca del 19% para min 3 jugadores.
Usamos vtree para observar la concentración de los datos por ejemplo para max_players, tenemos casi un 23% para máx 4 jugadores y cerca del 25% para máx 6 jugadores.
Descartar los fabricantes que no aportan data
Quitamos los fabricantes que no aportan data, volvemos a gráficar, ahora si esto se ve más claro!!

Quitamos los fabricantes que no aportan data, volvemos a gráficar, ahora si esto se ve más claro!!

¿Que se ha hecho hasta ahora?
Se realizó una exploración de datos, donde primero eliminalos columnas que no tienen mucha significancia en la predicción de nuestra variable turbine rated capacity, después vimos su correlación entre las existentes, y seleccionamos esas tres para analizarlas, también quité notes una variable con muchos missing values, entonces entre otras cosas más se hicieron como por ejemplo:
- hub_height_m: Le aplicamos un log porque su data tiene ambas colas muy sesgadas, pero aún así no funcionó la transformación y quedó igual , así que no se modifició
- rotor_diamater_m: Se revisó que tiene una correlación creciente , aunque tenía unos outliers con los fabricantes,
- manufacturers: después de analizarlos con root_diamater, y con su capacity, observé que en realidad algunos datos se perdían entre la correlación por lo que hice fue primero hacer una limpieza a los fabricantes y solo dejé los 4 que daban más significado al data con casi un 85%
Se tiene más claro cuales son las variables más significativas a la predicción, se hizo una limpieza, tenemos datos más contundentes con los cuales comenzar nuestra predicción, menos outliers sobre todo.
rpart library for decision tree
Leemos nuestro dataset
max_players max_playtime min_age min_players min_playtime playing_time
Min. : 0.00 Min. :30 Min. :0.000 Min. :0 Min. :30 Min. :30
1st Qu.: 6.50 1st Qu.:45 1st Qu.:4.000 1st Qu.:2 1st Qu.:45 1st Qu.:45
Median :13.00 Median :60 Median :8.000 Median :4 Median :60 Median :60
Mean :12.33 Mean :50 Mean :5.333 Mean :4 Mean :50 Mean :50
3rd Qu.:18.50 3rd Qu.:60 3rd Qu.:8.000 3rd Qu.:6 3rd Qu.:60 3rd Qu.:60
Max. :24.00 Max. :60 Max. :8.000 Max. :8 Max. :60 Max. :60
NA's :3 NA's :3 NA's :3 NA's :3 NA's :3 NA's :3
average_rating users_rated
Min. :5.255 Min. : 56.0
1st Qu.:5.957 1st Qu.: 203.5
Median :6.658 Median : 351.0
Mean :6.382 Mean :1397.7
3rd Qu.:6.945 3rd Qu.:2068.5
Max. :7.232 Max. :3786.0
NA's :3 NA's :3
max_players max_playtime min_age min_players min_playtime playing_time
Min. :18 Min. :90 Min. :12 Min. :4 Min. :90 Min. :90
1st Qu.:18 1st Qu.:90 1st Qu.:12 1st Qu.:4 1st Qu.:90 1st Qu.:90
Median :18 Median :90 Median :12 Median :4 Median :90 Median :90
Mean :18 Mean :90 Mean :12 Mean :4 Mean :90 Mean :90
3rd Qu.:18 3rd Qu.:90 3rd Qu.:12 3rd Qu.:4 3rd Qu.:90 3rd Qu.:90
Max. :18 Max. :90 Max. :12 Max. :4 Max. :90 Max. :90
NA's :1 NA's :1 NA's :1 NA's :1 NA's :1 NA's :1
average_rating users_rated
Min. :7.357 Min. :3923
1st Qu.:7.357 1st Qu.:3923
Median :7.357 Median :3923
Mean :7.357 Mean :3923
3rd Qu.:7.357 3rd Qu.:3923
Max. :7.357 Max. :3923
NA's :1 NA's :1
Leemos nuestro dataset
En este caso usamos fread, de data table, proceedemos a leer:
regtree <- rpart(formula=average_rating~.,
data = train_rpart,
control = rpart.control(maxdepth = 3))
Error in eval(predvars, data, env) : object 'average_rating' not found
# Cálculo del predictor
test_rpart <- test_rpart %>% mutate(pred =predict(regtree, test_rpart, type = "vector"),
reg = 1:nrow(test_rpart),
dif_pred_coll = hub_height_m-pred)
ggplot(test_rpart)+
geom_point(aes(x=reg, y=dif_pred_coll))
#[1] 23529.06
MSE2 <- mean((test_rpart$pred - test_rpart$total_project_capacity_mw)^2)
Note that the echo = FALSE parameter was added to the code chunk to prevent printing of the R code that generated the plot.
LS0tDQp0aXRsZTogIkJvYXJkX0dhbWVzX1JlZ3Jlc3Npb25fUHJvamVjdCINCmF1dGhvcjogJ0FkcmlhbiBIb21lcm8gTW9yZW5vIEdhcmPDrWEtIGFkcmlhbi5tb3Jlbm9AaXRlc28ubXgsIEdhYnJpZWwgQWxlamFuZHJvIE1vcmFsZXMNCiAgUnVpei0gJw0KZGF0ZTogIjYvMjEvMjAyMSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIGRmX3ByaW50OiBwYWdlZA0KICBnaXRodWJfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICBkZXY6IGpwZWcNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdGhlbWU6IGNvc21vDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KLS0tDQoNCmBgYHtyIHNldHVwLCBlY2hvID0gRkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gNykNCmBgYA0KDQo8c3R5bGU+DQouZm9yY2VCcmVhayB7IC13ZWJraXQtY29sdW1uLWJyZWFrLWFmdGVyOiBhbHdheXM7IGJyZWFrLWFmdGVyOiBjb2x1bW47IH0NCjwvc3R5bGU+DQoNCjxjZW50ZXI+DQohW10oLi9pbWFnZXMvaXRlc28uanBlZyl7d2lkdGg9MjAlfQ0KDQoNCjwvY2VudGVyPg0KDQojIyBJbnRyb2R1Y2Npw7NuDQoNCkpvc8OpIGVzIHVuIGRpc2XDsWFkb3IgZGUganVlZ29zIGRlIG1lc2EuIENyZWEgbGFzIHJlZ2xhcywgZGlzZcOxYSBsb3MgZ3LDoWZpY29zLCBlc2NvZ2Ugc3UgdGVtYSwgbsO6bWVybyBkZSBqdWdhZG9yZXMgeSBkdXJhY2nDs24gcHJvbWVkaW8gZGVsIGp1ZWdvIHF1ZSB0aWVuZSBlbiBtZW50ZS4gSm9zw6kgZXMgdW5hIHBlcnNvbmEgdMOtbWlkYSwgeSBhIHBlc2FyIGRlIHF1ZSBzdXMganVlZ29zIHN1ZWxlbiBndXN0YXJsZSBhIHN1cyBhbWlnb3MsIMOpbCBudW5jYSBoYSBxdWVyaWRvIHB1YmxpY2FybG9zIHBvciBtaWVkbyBhIHF1ZSBubyBzZWFuIGJpZW4gcmVjaWJpZG9zLiBTZSBxdWllcmUgZGVtb3N0cmFyIGEgSm9zw6ksIGNvbiB1bmEgYmFzZSBkZSBkYXRvcyBkZSBjYWxpZmljYWNpb25lcyBoaXN0w7NyaWNhcyBkZSBqdWVnb3MgZGUgbWVzYSwgY8OzbW8gaHViaWVyYW4gc2lkbyByZWNpYmlkb3Mgc3VzIGp1ZWdvcyBlbiBwcm9tZWRpbyBlbiBsYSDDqXBvY2EgcXVlIGxvcyBmdWUgY3JlYW5kby4NCg0KTG9zIGRhdG9zIGEgdXRpbGl6YXIgdmllbmVuIGRlIGVzdGEgYmFzZSBkZSBkYXRvczogDQpbKGJvYXJkX2dhbWVzKV0oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS90cmVlL21hc3Rlci9kYXRhLzIwMTkvMjAxOS0wMy0xMikqDQpxdWUsIGVuIGNhbWJpbywgdmllbmVuIGRlIGxhIHDDoWdpbmEgQm9hcmQgR2FtZSBHZWVrLg0KDQojIyBJbnN0YWxhY2nDs24gZGUgUGFxdWV0ZXMNCg0KUHJvY2VkZW1vcyBwYXJhIGVtcGV6YXIgZW4gaW5zdGFsYXIgbG9zIHNpZ3VpZW50ZXMgcGFxdWV0ZXMsIHNlIHB1ZWRlIG9taXRpciBlc3RlIHBhc28gc2kgeWEgc2UgdGllbmVuIHByZXZpYW1lbnRlIGluc3RhbGFkb3MuIEFxdcOtIHVuYSBsaXN0YSBkZSBsb3MgY3VhbGVzIHZhbW9zIGEgbmVjZXNpdGFyLg0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhLnRhYmxlIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJoMm8iKQ0KI2luc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KI2luc3RhbGwucGFja2FnZXMoImdndGhlbWVzIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhLnRyZWUiKQ0KI2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQojaW5zdGFsbC5wYWNrYWdlcygibW9kZWxkYXRhIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJEYXRhRXhwbG9yZXIiKQ0KI2luc3RhbGwucGFja2FnZXMoInZ0cmVlIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJjYVRvb2xzIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIpDQojaW5zdGFsbC5wYWNrYWdlcygicnBhcnQucGxvdCIpDQojaW5zdGFsbC5wYWNrYWdlcygibGFyZXMiKQ0KYGBgDQojIyBDYXJnYXIgTGlicmVyw61hcw0KIA0KVXNhbmRvICdsaWJyYXJ5JyBjYXJnYW1vcyBsYXMgbGlicmVyw61hcywgY29uIGxhcyBjdWFsZXMgdmFzIGEgaGFjZXIgdXNvIGRlIGxhcyBkaWZlcmVudGVzIGZ1bmNpb25lcy4gDQoNCmBgYHtyfQ0KbGlicmFyeSgiZGF0YS50YWJsZSIpDQpsaWJyYXJ5KCJoMm8iKQ0KbGlicmFyeSgiZ2dwbG90MiIpDQpsaWJyYXJ5KCJnZ3RoZW1lcyIpDQpsaWJyYXJ5KCJkYXRhLnRyZWUiKQ0KbGlicmFyeSgidGlkeXZlcnNlIikNCmxpYnJhcnkoIm1vZGVsZGF0YSIpDQpsaWJyYXJ5KCJEYXRhRXhwbG9yZXIiKQ0KbGlicmFyeSgidnRyZWUiKQ0KbGlicmFyeSgiY2FUb29scyIpDQpsaWJyYXJ5KCJycGFydCIpDQpsaWJyYXJ5KCJycGFydC5wbG90IikNCmxpYnJhcnkoImxhcmVzIikNCmBgYA0KDQojIyDDgW5hbGlzaXMgRGVzY3JpcHRpdm8sIERhdGEgRW5naW5lZXJpbmcNCiANCiMjIyBMZWVtb3MgbnVlc3RybyBkYXRhc2V0DQoNCkVuIGVzdGUgY2FzbyB1c2Ftb3MgcmVhZC5jc3YsIHByb2NlZWRlbW9zIGEgbGVlcjoNCg0KYGBge3J9DQpib2FyZF9nYW1lcyA8LSByZWFkLmNzdigiLi9ib2FyZF9nYW1lcy5jc3YiKSANCmBgYA0KDQojIyMgT2JzZXJ2YWNpw7NuIGRlIGxhcyBwcmltZXJhcyBsw61uZWFzDQoNClBhcmEgZWplbXBsaWZpY2FyIHVzYXJlbW9zIGVsIGRhdGEgZnJhbWUgYHR1cmJpbmVzYCwgeSBjb24gbGEgZnVuY2nDs24gYGhlYWQoKWAgc29sbyBtb3N0cmFyZW1vcyBsYXMgcGlybWVyYXMgNiBsw61uZWFzIGRlIGVzdGU6IA0KDQotIGdhbWVfaWQJSWRlbnRpZmljYWRvciDDum5pY28NCi0gZGVzY3JpcHRpb24JRGVzY3JpcGNpw7NuIGNvcnRhDQotIGltYWdlCVVSTCBjb24gaW1hZ2VuIGRlbCBqdWVnbw0KLSBtYXhfcGxheWVycwlKdWdhZG9yZXMgbcOheGltb3MNCi0gbWF4X3BsYXl0aW1lCVRpZW1wbyBtw6F4aW1vIGRlIGp1ZWdvDQotIG1pbl9hZ2UJRWRhZCBtw61uaW1hDQotIG1pbl9wbGF5ZXJzCUp1Z2Fkb3JlcyBtw61uaW1vcw0KLSBtaW5fcGxheXRpbWUJVGllbXBvIG3DrW5pbW8gZGUganVlZ28NCi0gbmFtZQlOb21icmUgZGVsIGp1ZWdvDQotIHBsYXlpbmdfdGltZQlUaWVtcG8gcHJvbWVkaW8gZGUganVlZ28NCi0gdGh1bWJuYWlsCVVSTCBjb24gdGh1bWJuYWlsIGRlbCBqdWVnbw0KLSB5ZWFyX3B1Ymxpc2hlZAlBw7FvIGRlIHB1YmxpY2FjacOzbg0KLSBhcnRpc3QJRGlzZcOxYWRvciBncsOhZmljbyBkZWwganVlZ28NCi0gY2F0ZWdvcnkJQ2F0ZWdvcsOtYXMgZGVsIGp1ZWdvIChzZXBhcmFkYXMgcG9yIGNvbWEpDQotIGNvbXBpbGF0aW9uCVNpIGVzIHBhcnRlIGRlIHVuYSBjb21waWxhY2nDs24sIG5vbWJyZSBkZSBsYSBjb21waWxhY2nDs24NCi0gZGVzaWduZXIJRGlzZcOxYWRvciBkZWwganVlZ28NCi0gZXhwYW5zaW9uCVNpIGhheSB1bmEgZXhwYW5zacOzbiwgZWwgbm9tYnJlIGRlIGxhIGV4cGFuc2nDs24NCi0gZmFtaWx5CUZhbWlsaWEsIGVxdWl2YWxlbnRlIGEgZWRpdG9yYQ0KLSBtZWNoYW5pYwlNZWPDoW5pY2FzLCBzZXBhcmFkYXMgcG9yIGNvbWENCi0gcHVibGlzaGVyCUNvbXBhw7HDrWEgbyBwZXJzb25hIHF1ZSBwdWJsaWNhcm9uIGVsIGp1ZWdvIChzZXBhcmFkYXMgcG9yIGNvbWEpDQotIGF2ZXJhZ2VfcmF0aW5nCUNhbGlmaWNhY2nDs24gcHJvbWVkaW8gZW4gQm9hcmQgR2FtZSBHZWVrDQotIHVzZXJzX3JhdGVkCU7Dum1lcm8gZGUgdXN1YXJpb3MgcXVlIGNhbGlmaWNhcm9uIGVsIGp1ZWdvDQoNCmBgYHtyfQ0KaGVhZChib2FyZF9nYW1lcykNCmBgYA0KDQojIyMgQ29sbmFtZXMgZGUgbnVlc3RybyBkYXRhc2V0DQoNCkRlc3B1w6lzIGRlIHVuYSByw6FwaWRhIG9ic2VydmFjacOzbiwgZWplY3V0YW1vcyBsb3Mgc2lndWllbnRlcyBjb21hbmRvcyBwYXJhIGNvbmZpcm1hY2nDs246DQoNCmBgYHtyfQ0KY29sbmFtZXMoYm9hcmRfZ2FtZXMpDQpgYGANCg0KIyMjIFRpcG8gZGUgdmFyaWFibGVzDQoNClVzYW5kbyBkYXRhIGV4cGxvcmVyIG9ic2VydmFtb3MgZWwgdGlwbyBkZSB2YXJpYWJsZXMsIGNhc2kgdGVuZW1vcyBlbCBtaXNtbyBwb3JjZW50YWplIHBhcmEgbGFzIGRpc2NyZXRhcyB5IGNvbnRpbnVhLCB5IHRlbmVtb3MgdW4gYmFqbyBwb3JjZW50YWplIGRlIG1pc3NpbmcgdmFsdWVzOg0KDQotIFPDs2xvIGVsIDAuOTklIGRlIGxhcyBmaWxhcyBlc3TDoW4gY29tcGxldGFzLA0KLSB0ZW5lbW9zIDExLjU0JSBkZSBvYnNlcnZhY2lvbmVzIGZhbHRhbnRlcywgZXMgZGVjaXIsIGRhZG8gcXVlIHNvbG8gdGVuZW1vcyAwLjk5JSBkZSBsYXMgZmlsYXMgY29tcGxldGFzLCBzb2xvIGhheSAxMC41NSUgZGUgb2JzZXJ2YWNpb25lcyBmYWx0YW50ZXMgZGVsIHRvdGFsLg0KDQpFc3RvcyB2YWxvcmVzIGZhbHRhbnRlcyBub3MgcG9kcsOhbiBnZW5lcmFsIHByb2JsZW1hcyBwYXJhIGFuYWxpemFyIGxvcyBkYXRvcywgdmVhbW9zIHVuIHBvY28gbG9zIHBlcmZpbGVzIHF1ZSBmYWx0YW4uDQoNCmBgYHtyIGJhcnBsb3R9DQpwbG90X2ludHJvKGJvYXJkX2dhbWVzKQ0KYGBgDQoNCiMjIyBNaXNzaW5nIHBsb3QNCg0KUGFyYSB2aXN1YWxpemFyIGVsIHBlcmZpbCBkZSBsb3MgZGF0b3MgZmFsdGFudGVzIHBvZGVtb3MgdXRpbGl6YXIgbGEgZnVuY2nDs24gcGxvdF9taXNzaW5nKCkuIEVuIGxhIHZpc3VhbGl6YWNpw7NuIGRlYmFqbywgcG9kZW1vcyB2ZXIgcXVlIGxhIHZhcmlhYmxlcyBjb21waWxhdGlvbiB5IGV4cGFuc2lvbiwgc29uIGxhcyBxdWUgbGVzIGZhbHRhIGluZm9ybWFjacOzbiwgZW5jb250cmFtb3MgZGUgcXVlIHPDs2xvIGVsIDIuNjMlIChjb21waWxhdGlvbiksIDE2LjU0JSAoZXhwYW5zaW9uKSBkZSBudWVzdHJhcyBmaWxhcyBlc3TDqW4gY29tcGxldGFzIHkgcHJvYmFibGVtZW50ZSBlc3RhIHZhcmlibGUgbm8gc2VhIGRlIG11Y2hhIGluZm9tYWNpw7NuLiBQb3IgdGFudG8gbGEgcG9kZW1vcyBlbGltaW5hciBkZSBudWVzdHJvIGRhdGFmcmFtZSwgYWhvcml0YSBtaXNtbyEhDQoNCmBgYHtyfQ0KcGxvdF9taXNzaW5nKGJvYXJkX2dhbWVzKQ0KYGBgDQoNCiMjIyBFbGltaW5hbW9zIGxhIGNvbHVtbmEgcXVlIHRpZW5lcyBtw6FzIG1pc3NpbmcgdmFsdWVzDQoNCkVsaW1pbmFtb3Mgbm90ZXMgZGUgbnVlc3RybyBkYXRhZnJhbWU6DQoNCmBgYHtyfQ0KZmluYWxfYm9hcmRfZ2FtZXMgPC0gZHJvcF9jb2x1bW5zKGJvYXJkX2dhbWVzLCBjKCJjb21waWxhdGlvbiIsImV4cGFuc2lvbiIpKQ0KDQpjb2xuYW1lcyhmaW5hbF9ib2FyZF9nYW1lcykNCmBgYA0KDQoNCmBgYHtyfQ0KZmluYWxfYm9hcmRfZ2FtZXMgPC0gbmEub21pdChmaW5hbF9ib2FyZF9nYW1lcykgDQpgYGANCg0KIyMjIMOBbmFsaXNpcyBkZSBDb3JyZWxhY2nDs24NCg0KUG9kZW1vcyB2ZXIgbGEgbcOhcyBhbHRhIGNvcnJlbGFjacOzbiBlbiBlc3RhcyB2YXJpYWJsZXM6DQoNCi0gbWluX3BsYXl0aW1lLW1heF9wbGF5dGltZQ0KLSBtaW5fcGxheXRpbWUtbWluX2FnZQ0KLSBtaW5fcGxheXRpbWUtcGxheWluZ190aW1lDQotIGF2ZXJhZ2VfcmF0aW5nLW1pbl9hZ2UNCg0KYGBge3J9DQpwbG90X2NvcnJlbGF0aW9uKG5hLm9taXQoZmluYWxfYm9hcmRfZ2FtZXMpLCBtYXhjYXQgPSA1TCkNCmBgYA0KQWhvcmEgZGUgdW5hIG1hbmVyYSBtw6FzIGRldGFsbGFkYSB2YW1vcyBhIGFuYWxpemFyIGxhcyB2YXJpYWJsZXMgbcOhcyBjb3JyZWxhY2lvbmFkYXMgZW50cmUgc2ksIGVzdGUgdG9wIGRlIDEwOg0KDQpgYGB7cn0NCmNvcnJfY3Jvc3MoZmluYWxfYm9hcmRfZ2FtZXMsICMgbmFtZSBvZiBkYXRhc2V0DQogIG1heF9wdmFsdWUgPSAwLjA1LCAjIGRpc3BsYXkgb25seSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbnMgKGF0IDUlIGxldmVsKQ0KICB0b3AgPSAxMCAjIGRpc3BsYXkgdG9wIDEwIGNvdXBsZXMgb2YgdmFyaWFibGVzIChieSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCkNCikNCmBgYA0KIyMjIFFRIHBsb3QNCg0KTGEgZ3LDoWZpY2EgUXVhbnRpbGUtUXVhbnRpbGUgZXMgdW5hIGZvcm1hIGRlIHZpc3VhbGl6YXIgbGEgZGVzdmlzacOzbiBkZSB1bmEgZGlzdHJpYnVjacOzbiBkZSBwcm9iYWJpbGlkYWQgZXNwZWPDrWZpY2EuDQoNCkRlc3B1w6lzIGRlIGFuYWxpemFyIGVzdG9zIGdyw6FmaWNvcywgYSBtZW51ZG8gZXMgYmVuZWZpY2lvc28gYXBsaWNhciB1bmEgdHJhbnNmb3JtYWNpw7NuIG1hdGVtw6F0aWNhIChjb21vIGxvZ2FyaXRtbykgcGFyYSBtb2RlbG9zIGNvbW8gbGEgcmVncmVzacOzbiBsaW5lYWwuIFBhcmEgaGFjZXJsbywgcG9kZW1vcyB1c2FyIGxhIGZ1bmNpw7NuIHBsb3RfcXEuIERlIGZvcm1hIHByZWRldGVybWluYWRhLCBzZSBjb21wYXJhIGNvbiBsYSBkaXN0cmlidWNpw7NuIG5vcm1hbC4NCg0KYGBge3J9DQpxcV9kYXRhIDwtIGZpbmFsX2JvYXJkX2dhbWVzWywgYygibWluX3BsYXl0aW1lIiwgIm1heF9wbGF5dGltZSIsICJtaW5fYWdlIiwgInBsYXlpbmdfdGltZSIsICJhdmVyYWdlX3JhdGluZyIpXQ0KDQpwbG90X3FxKHFxX2RhdGEsIHNhbXBsZWRfcm93cyA9IDEwMDBMKQ0KDQpgYGANCkVuIGVsIGdyw6FmaWNvLCBsYXMgY29sdW1uYXMgcGFyZWNlbiBzZXNnYWRhcyBlbiBhbWJhcyBjb2xhcy4gQXBsaXF1ZW1vcyB1bmEgdHJhbnNmb3JtYWNpw7NuIGxvZ2Fyw610bWljYSBzaW1wbGUgeSBncmFmaXF1ZW1vcyBkZSBudWV2by4gQXZlcmFnZSBSYXRpbmcgbm8gbWVqb3LDsy4gR2FibyBzaSB2ZXMgcXVlIGVzIG5lY2VzYXJpbyBwb2RlbW9zIGhhY2VybGUgdXBkYXRlIGEgbGFzIGNvbHVtbmFzIGNvbiBsYSB0cmFuc2Zvcm1hY2nDs24uDQpgYGB7cn0NCmxvZ19xcV9kYXRhIDwtIHVwZGF0ZV9jb2x1bW5zKHFxX2RhdGEsIDE6NSwgZnVuY3Rpb24oeCkgbG9nKHggKyAxKSkNCg0KDQpwbG90X3FxKGxvZ19xcV9kYXRhLCBzYW1wbGVkX3Jvd3MgPSAxMDAwTCkNCg0KYGBgDQoNCiMjIyDDgW5hbGlzaXMgRXhwbG9yYXRvcmlvIGRlIGxvcyBEYXRvcw0KVGVuaWVuZG8gbnVlc3RyYXMgdmFyaWFibGVzIGNvbiBtYXlvciBjb3JyZWxhY2nDs24gdmFtb3MgYSBncsOhZmljYXJsYXMgY29uIGdlb20gcG9pbnQuLjoNCg0KLSBtaW5fcGxheXRpbWUtbWF4X3BsYXl0aW1lDQoNCg0KYGBge3J9DQpmaW5hbF9ib2FyZF9nYW1lcyAlPiUgIGdncGxvdChhZXMoeCA9IG1pbl9wbGF5dGltZSwgeSA9IG1heF9wbGF5dGltZSkpICsgDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCi0gYXZlcmFnZV9yYXRpbmctbWluX2FnZQ0KDQoNCmBgYHtyfQ0KZmluYWxfYm9hcmRfZ2FtZXMgJT4lICBnZ3Bsb3QoYWVzKHggPSBhdmVyYWdlX3JhdGluZywgeSA9IG1pbl9hZ2UpKSArIA0KICBnZW9tX3BvaW50KCkNCmBgYA0KDQotIGF2ZXJhZ2VfcmF0aW5nLXBsYXlpbmdfdGltZQ0KDQoNCmBgYHtyfQ0KZmluYWxfYm9hcmRfZ2FtZXMgJT4lICBnZ3Bsb3QoYWVzKHggPSBwbGF5aW5nX3RpbWUsIHkgPSBhdmVyYWdlX3JhdGluZykpICsgDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCi0gdXNlcnNfcmF0ZWQtYXZlcmFnZV9yYXRpbmcNCg0KDQpgYGB7cn0NCmZpbmFsX2JvYXJkX2dhbWVzICU+JSAgZ2dwbG90KGFlcyh4ID0gdXNlcnNfcmF0ZWQsIHkgPSBhdmVyYWdlX3JhdGluZykpICsgDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KIyMjVXNpbmcgdnRyZWUgcGFyYSBleHBsb3Jhcg0KDQpVc2Ftb3MgdnRyZWUgcGFyYSBvYnNlcnZhciBsYSBjb25jZW50cmFjacOzbiBkZSBsb3MgZGF0b3MgcG9yIGVqZW1wbG8gcGFyYSBtaW5fYWdlLCBkb25kZSBsYSBtYXlvcsOtYSBkZSBsb3MgZGF0b3Mgc2UgY29uY2VudHJhbiBlbiBtaW5fYWdlIGRlIDggYcOxb3MsIDEwIGHDsW9zIHkgMTIgYcOxb3MuDQoNCmBgYHtyfQ0KdnRyZWUoZmluYWxfYm9hcmRfZ2FtZXMsICJtaW5fYWdlIikNCmBgYA0KDQpVc2Ftb3MgdnRyZWUgcGFyYSBvYnNlcnZhciBsYSBjb25jZW50cmFjacOzbiBkZSBsb3MgZGF0b3MgcG9yIGVqZW1wbG8gcGFyYSBtaW5fcGxheWVycywgdGVuZW1vcyBjYXNpIHVuIDY5JSBwYXJhIG1pbiAyIGp1Z2Fkb3JlcyB5IGNlcmNhIGRlbCAxOSUgcGFyYSBtaW4gMyBqdWdhZG9yZXMuDQoNCmBgYHtyfQ0KdnRyZWUoZmluYWxfYm9hcmRfZ2FtZXMsICJtaW5fcGxheWVycyIpDQpgYGANCg0KDQpVc2Ftb3MgdnRyZWUgcGFyYSBvYnNlcnZhciBsYSBjb25jZW50cmFjacOzbiBkZSBsb3MgZGF0b3MgcG9yIGVqZW1wbG8gcGFyYSBtYXhfcGxheWVycywgdGVuZW1vcyBjYXNpIHVuIDIzJSBwYXJhIG3DoXggNCBqdWdhZG9yZXMgeSBjZXJjYSBkZWwgMjUlIHBhcmEgbcOheCA2IGp1Z2Fkb3Jlcy4NCg0KYGBge3J9DQp2dHJlZShmaW5hbF9ib2FyZF9nYW1lcywgIm1heF9wbGF5ZXJzIikNCmBgYA0KDQojIyMgRGVzY2FydGFyIGxvcyBmYWJyaWNhbnRlcyBxdWUgbm8gYXBvcnRhbiBkYXRhDQoNClF1aXRhbW9zIGxvcyBmYWJyaWNhbnRlcyBxdWUgbm8gYXBvcnRhbiBkYXRhLCB2b2x2ZW1vcyBhIGdyw6FmaWNhciwgYWhvcmEgc2kgZXN0byBzZSB2ZSBtw6FzIGNsYXJvISENCg0KYGBge3J9DQpmaW5hbF9ib2FyZF9nYW1lcyA8LSBmaW5hbF9ib2FyZF9nYW1lcyAlPiUgZmlsdGVyKG1pbl9wbGF5ZXJzICVpbiUgYygnMCcsJzQnLCc4JykpDQoNCmZpbmFsX2JvYXJkX2dhbWVzICU+JSAgZ2dwbG90KCkgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBtaW5fcGxheWVycykpICsNCiAgZ2d0aXRsZSgiQ291bnQgb2YgQm9hcmQgR2FtZXMgYnkgbWluX3BsYXllcnMiKQ0KYGBgDQoNClF1aXRhbW9zIGxvcyBmYWJyaWNhbnRlcyBxdWUgbm8gYXBvcnRhbiBkYXRhLCB2b2x2ZW1vcyBhIGdyw6FmaWNhciwgYWhvcmEgc2kgZXN0byBzZSB2ZSBtw6FzIGNsYXJvISENCg0KYGBge3J9DQpmaW5hbF9ib2FyZF9nYW1lcyA8LSBmaW5hbF9ib2FyZF9nYW1lcyAlPiUgZmlsdGVyKG1heF9wbGF5ZXJzICVpbiUgYygnMCcsJzknLCcxMicsJzEzJywnMTUnLCcxOCcsJzI0JywnOTknKSkNCg0KZmluYWxfYm9hcmRfZ2FtZXMgJT4lICBnZ3Bsb3QoKSArDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeCA9IG1heF9wbGF5ZXJzKSkgKw0KICBnZ3RpdGxlKCJDb3VudCBvZiBCb2FyZCBHYW1lcyBieSBtYXhfcGxheWVycyIpDQpgYGANCg0KIyMjIMK/UXVlIHNlIGhhIGhlY2hvIGhhc3RhIGFob3JhPw0KDQpTZSByZWFsaXrDsyB1bmEgZXhwbG9yYWNpw7NuIGRlIGRhdG9zLCBkb25kZSBwcmltZXJvIGVsaW1pbmFsb3MgY29sdW1uYXMgcXVlIG5vIHRpZW5lbiBtdWNoYSBzaWduaWZpY2FuY2lhIGVuIGxhIHByZWRpY2Npw7NuIGRlIG51ZXN0cmEgdmFyaWFibGUgdHVyYmluZSByYXRlZCBjYXBhY2l0eSwgZGVzcHXDqXMgdmltb3Mgc3UgY29ycmVsYWNpw7NuIGVudHJlIGxhcyBleGlzdGVudGVzLCB5IHNlbGVjY2lvbmFtb3MgZXNhcyB0cmVzIHBhcmEgYW5hbGl6YXJsYXMsIHRhbWJpw6luIHF1aXTDqSBub3RlcyB1bmEgdmFyaWFibGUgY29uIG11Y2hvcyBtaXNzaW5nIHZhbHVlcywgZW50b25jZXMgZW50cmUgb3RyYXMgY29zYXMgbcOhcyBzZSBoaWNpZXJvbiBjb21vIHBvciBlamVtcGxvOiANCg0KLSBodWJfaGVpZ2h0X206IExlIGFwbGljYW1vcyB1biBsb2cgcG9ycXVlIHN1IGRhdGEgdGllbmUgYW1iYXMgY29sYXMgbXV5IHNlc2dhZGFzLCBwZXJvIGHDum4gYXPDrSBubyBmdW5jaW9uw7MgbGEgdHJhbnNmb3JtYWNpw7NuIHkgcXVlZMOzIGlndWFsICwgYXPDrSBxdWUgbm8gc2UgbW9kaWZpY2nDsw0KLSByb3Rvcl9kaWFtYXRlcl9tOiBTZSByZXZpc8OzIHF1ZSB0aWVuZSB1bmEgY29ycmVsYWNpw7NuIGNyZWNpZW50ZSAsIGF1bnF1ZSB0ZW7DrWEgdW5vcyBvdXRsaWVycyBjb24gbG9zIGZhYnJpY2FudGVzLA0KLSBtYW51ZmFjdHVyZXJzOiBkZXNwdcOpcyBkZSBhbmFsaXphcmxvcyBjb24gcm9vdF9kaWFtYXRlciwgeSBjb24gc3UgY2FwYWNpdHksIG9ic2VydsOpIHF1ZSBlbiByZWFsaWRhZCBhbGd1bm9zIGRhdG9zIHNlIHBlcmTDrWFuIGVudHJlIGxhIGNvcnJlbGFjacOzbiBwb3IgbG8gcXVlIGhpY2UgZnVlIHByaW1lcm8gaGFjZXIgdW5hIGxpbXBpZXphIGEgbG9zIGZhYnJpY2FudGVzIHkgc29sbyBkZWrDqSBsb3MgNCBxdWUgZGFiYW4gbcOhcyBzaWduaWZpY2FkbyBhbCBkYXRhIGNvbiBjYXNpIHVuIDg1JQ0KDQpTZSB0aWVuZSBtw6FzIGNsYXJvIGN1YWxlcyBzb24gbGFzIHZhcmlhYmxlcyBtw6FzIHNpZ25pZmljYXRpdmFzIGEgbGEgcHJlZGljY2nDs24sIHNlIGhpem8gdW5hIGxpbXBpZXphLCB0ZW5lbW9zIGRhdG9zIG3DoXMgY29udHVuZGVudGVzIGNvbiBsb3MgY3VhbGVzIGNvbWVuemFyIG51ZXN0cmEgcHJlZGljY2nDs24sIG1lbm9zIG91dGxpZXJzIHNvYnJlIHRvZG8uDQoNCg0KDQoNCiMjIHJwYXJ0IGxpYnJhcnkgZm9yIGRlY2lzaW9uIHRyZWUNCiANCiMjIyBMZWVtb3MgbnVlc3RybyBkYXRhc2V0DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMCkgICMgTGEgc2VtaWxsYQ0KZmluYWxfYm9hcmRfZ2FtZXNfbiA8LSBmaW5hbF9ib2FyZF9nYW1lc1tjKDQsNSw2LDcsOCwxMCwxOSwyMCldDQpmaW5hbF9ib2FyZF9nYW1lc19ycGFydCA8LSBhcy5kYXRhLmZyYW1lKGZpbmFsX2JvYXJkX2dhbWVzX24pDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChmaW5hbF9ib2FyZF9nYW1lc19ycGFydCxTcGxpdFJhdGlvPTAuOCkgIyBMb3MgZGF0b3MgZGUgdHJhaW4gc2Vyw6FuIGRlIDgwJQ0KdHJhaW5fcnBhcnQgPSBzdWJzZXQoZmluYWxfYm9hcmRfZ2FtZXNfcnBhcnQsc3BsaXQgPT0gVFJVRSkNCnRlc3RfcnBhcnQgPSBzdWJzZXQoZmluYWxfYm9hcmRfZ2FtZXNfcnBhcnQsc3BsaXQgPT0gRkFMU0UpDQpgYGANCg0KDQpgYGB7cn0NCnN1bW1hcnkodHJhaW5fcnBhcnQpDQpzdW1tYXJ5KHRlc3RfcnBhcnQpDQpgYGANCg0KIyMjIExlZW1vcyBudWVzdHJvIGRhdGFzZXQNCg0KRW4gZXN0ZSBjYXNvIHVzYW1vcyBmcmVhZCwgZGUgZGF0YSB0YWJsZSwgcHJvY2VlZGVtb3MgYSBsZWVyOg0KDQpgYGB7cn0NCiMgTW9kZWxvIGRlIHJlZ3Jlc2nDs24gY29uIGVsIGNvbmp1bnRvIGRlIGRhdG9zIHRyYWluDQojIE1vZGVsbyBkZSByZWdyZXNpw7NuIGNvbiBlbCBjb25qdW50byBkZSBkYXRvcyB0cmFpbg0KcmVndHJlZSA8LSBycGFydChmb3JtdWxhPWF2ZXJhZ2VfcmF0aW5nfi4sIA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fcnBhcnQsIA0KICAgICAgICAgICAgICAgICBjb250cm9sID0gcnBhcnQuY29udHJvbChtYXhkZXB0aCA9ICAzKSkNCg0KIyBWaXN1YWxpemFjacOzbiBkZWwgw6FyYm9sIGRlIGRlY2lzacOzbg0KcnBhcnQucGxvdChyZWd0cmVlLCBib3gucGFsZXR0ZSA9ICJSZEJ1IiwgZGlnaXRzPSAtMykNCmBgYA0KDQpgYGB7cn0NCiMgQ8OhbGN1bG8gZGVsIHByZWRpY3Rvcg0KdGVzdF9ycGFydCA8LSB0ZXN0X3JwYXJ0ICU+JSAgbXV0YXRlKHByZWQgPXByZWRpY3QocmVndHJlZSwgdGVzdF9ycGFydCwgdHlwZSA9ICJ2ZWN0b3IiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByZWcgPSAxOm5yb3codGVzdF9ycGFydCksDQogICAgICAgICAgICAgICAgICAgICAgICAgZGlmX3ByZWRfY29sbCA9IGh1Yl9oZWlnaHRfbS1wcmVkKQ0KDQpnZ3Bsb3QodGVzdF9ycGFydCkrDQogIGdlb21fcG9pbnQoYWVzKHg9cmVnLCB5PWRpZl9wcmVkX2NvbGwpKQ0KDQojWzFdIDIzNTI5LjA2DQpNU0UyIDwtIG1lYW4oKHRlc3RfcnBhcnQkcHJlZCAtIHRlc3RfcnBhcnQkdG90YWxfcHJvamVjdF9jYXBhY2l0eV9tdyleMikNCmBgYA0KDQoNCg0KDQpOb3RlIHRoYXQgdGhlIGBlY2hvID0gRkFMU0VgIHBhcmFtZXRlciB3YXMgYWRkZWQgdG8gdGhlIGNvZGUgY2h1bmsgdG8gcHJldmVudCBwcmludGluZyBvZiB0aGUgUiBjb2RlIHRoYXQgZ2VuZXJhdGVkIHRoZSBwbG90Lg0K